class Goal < ActiveRecord::Base
  has_many :goal_checkpoints, :dependent => :destroy
  has_many :comments, :as => :commentable, :dependent => :destroy
  has_many :goals, :foreign_key => "parent_id", :dependent => :destroy
  belongs_to :parent_goal, 
    :class_name => "goal",  
    :foreign_key => "parent_id"
    
  ITEMS_PER_PAGE = 8
  GOAL_INTERVAL_UNITS = ["day", "week", "month"]
  
  COMPLETE_BY_DATE = 0
  COMPLETE_NEGATIVE = 1
  COMPLETE_POSITIVE = 2
  
  validate :proper_duration_and_interval
  after_create :create_goal_checkpoints
  after_validation_on_update :update_goal_checkpoints
    
  def self.get_items(count = ITEMS_PER_PAGE, page = nil)
    page = cleaned_page(page)
    
    find(
      :all,
      :conditions => {
        :parent_id  => 0,
        :completed  => [COMPLETE_BY_DATE, COMPLETE_NEGATIVE]
      },
      :offset     => (page - 1) * count,
      :limit      => count,
      :order      => "priority DESC, created_at DESC",
      :include    => :goal_checkpoints
    )
  end

  def self.get_counts(page_size = ITEMS_PER_PAGE)
    counts = count
    pages = ((counts - 1)/ page_size) + 1
    [counts, pages]
  end
  
  def self.get_completed_items()
    find(
      :all,
      :conditions => {
        :parent_id  => 0,
        :completed  => COMPLETE_POSITIVE
      },
      :order      => "created_at DESC",
      :include    => :goal_checkpoints
    )
  end

  
  def proper_duration_and_interval
    errors.add_to_base("Invalid interval / interval unit") if 
      (!interval && interval_unit) ||
      (interval && !interval_unit) ||
      (interval == 0)
    
    if (interval && interval_unit)
      errors.add_to_base("Invalid interval unit") if !GOAL_INTERVAL_UNITS.include?(interval_unit)
      errors.add_to_base("Invalid start date") if !begin_at.is_a?(Time)
      errors.add_to_base("Invalid end date") if !end_at.is_a?(Time) || (begin_at.is_a?(Time) && begin_at > end_at)
    else
      # No interval
      errors.add_to_base("Invalid end date") if !end_at.is_a?(Time) || end_at < Time.now.utc
    end
  end

  def progress
    @progress ||= 
      begin
        progress_count = [0, 0, 0]  # [Pending, Success, Failure]
        goal_checkpoints.each do |checkpoint|
          progress_count[checkpoint.state] += 1
        end
        
        total = progress_count[0] + progress_count[1] + progress_count[2]
        
        Struct.new(
          :total,
          :pending,
          :success,
          :failure
        ).new(total, progress_count[0], progress_count[1], progress_count[2])
      end
  end
  
  def progress_percentages
    @progress_percentages ||=
      begin
        success   = (progress.success.to_f / progress.total * 100).ceil
        failure   = (progress.failure.to_f / progress.total * 100).ceil
        pending   = 100 - success - failure
        
        Struct.new(
          :pending,
          :success,
          :failure
        ).new(pending, success, failure)
      end
  end
  
  def completed?
    case completed
      when COMPLETE_POSITIVE
        true
      when COMPLETE_NEGATIVE
        false
      when COMPLETE_BY_DATE
        self.end_at < Time.now.utc
    end
  end
  
  def create_goal_checkpoints(success = [], failure = [])
    if (interval && interval_unit)
      t = self.begin_at
      period = interval.send(interval_unit)
      while (t < self.end_at)
        gc = GoalCheckpoint.new(:goal_id => self.id, :targetted_at => t)
        gc.state = get_state(t, success, failure)
        gc.save
        t += period
      end
      
      # Terminating checkpoint.
      gc = GoalCheckpoint.new(:goal_id => self.id, :targetted_at => self.end_at)
      gc.state = get_state(self.end_at, success, failure)
      gc.save
    else
      # No interval, just one checkpoint at end_at
      gc = GoalCheckpoint.new(:goal_id => self.id, :targetted_at => self.end_at)
      gc.state = get_state(self.end_at, success, failure)
      gc.save
    end
  end
  
  def update_goal_checkpoints
    if (interval_changed? || interval_unit_changed? || begin_at_changed? || end_at_changed?)
      # Keep the dates of any succeeded or failed checkpoints.  Then delete the checkpoint.
      success = []
      failure = []
      goal_checkpoints.each do |checkpoint|
        success << checkpoint.targetted_at.to_date if checkpoint.state == GoalCheckpoint::STATE_SUCCESS
        failure << checkpoint.targetted_at.to_date if checkpoint.state == GoalCheckpoint::STATE_FAILURE
        checkpoint.delete
      end
      
      create_goal_checkpoints(success, failure)
    end
  end
  
  private 
    def get_state(date, success, failure)
      date = date.to_date if date.is_a?(Time)
      return GoalCheckpoint::STATE_SUCCESS if success.include?(date)
      return GoalCheckpoint::STATE_FAILURE if failure.include?(date)
      return GoalCheckpoint::STATE_PENDING
    end
end
